package zoner

import (
	c "github.com/Unixeno/TheMoon/common/zone"
	"github.com/Unixeno/TheMoon/logging"
	model2 "github.com/Unixeno/TheMoon/model"
	"github.com/Unixeno/TheMoon/planet/core/context"
	"github.com/Unixeno/TheMoon/planet/orm/model"
	"github.com/Unixeno/TheMoon/planet/orm/moon"
	"github.com/Unixeno/TheMoon/planet/orm/zone"
	"github.com/Unixeno/TheMoon/pool"
	pb "github.com/Unixeno/TheMoon/radio/protos"
	"sync"
	"time"
)

const ModuleName string = "zoner"

var logger = logging.GetLogger(ModuleName)
var localZoner = &Zoner{}

type Zoner struct {
	zoneVersion sync.Map // map[zoneID] (int)target version, 当前所有zone的目标id
}

func GetModule() *Zoner {
	return localZoner
}

func (zoner *Zoner) Init() {
	context.RegisterModule(ModuleName)
	logger.Debug("init zoner")
	dao := zone.New()
	zones, err := dao.List()
	if err != nil {
		logger.WithError(err).Error()
		context.Cancel()
	}
	for _, z := range zones {
		zoner.zoneVersion.Store(z.ID, z.Version)
	}
	logger.Debugf("found %d zones", len(zones))
}

func (zoner *Zoner) Start() {
	logger.Debug("start zoner")
	go zoner.worker()
}

func (zoner *Zoner) worker() {
	for {
		message, err := context.RecvMessage(ModuleName)
		if err != nil {
			logger.WithError(err).Error("failed to recv message")
			return
		}
		data, ok := message.Content.([]byte)
		if !ok {
			logger.Error("unknown message type")
			continue
		}

		switch message.Operation {
		case c.OpSync:
			syncMsg := &pb.ZoneSyncMessage{}
			if err := syncMsg.Unmarshal(data); err != nil {
				logger.WithError(err).Error("invalid message content")
				continue
			}
			zoner.handleSync(message.MoonID, syncMsg)
		case c.OpFetch:
			_ = zoner.handleFetch(message.MoonID, message)
		}
	}
}

func (zoner *Zoner) handleFetch(moonID string, msg *model2.PlanetMessage) error {
	fetchMsg := &pb.ZoneFetch{}
	if err := fetchMsg.Unmarshal(msg.Content.([]byte)); err != nil {
		logger.WithError(err).Error("invalid message content")
	}
	logger.WithFields(logging.Fields{
		"moonID":  moonID,
		"zoneID":  fetchMsg.ZoneID,
		"version": fetchMsg.Version,
	}).Debug("handle fetch")

	reply := msg.CreateReplay()
	reply.Content = msg.Content
	reply.Operation = c.OpFetch
	m := &pb.ZoneFetch{}

	dao := zone.New()
	dao.ID = fetchMsg.ZoneID
	zonefile, err := dao.GetZonefile(int(fetchMsg.Version))
	if err != nil {
		logger.WithError(err).Error()
	} else {
		buf := pool.Fixed64KPool.Get().([]byte)
		defer pool.Fixed64KPool.Put(buf)

		m.ZoneID = zonefile.ZoneID
		m.Zonefile = zonefile.Rules
		m.Version = int32(zonefile.Version)
		n, err := m.MarshalTo(buf)
		if err == nil {
			reply.Content = buf[:n]
		}
	}
	return context.SendMessage(&reply)
}

func (zoner *Zoner) handleSync(moonID string, msg *pb.ZoneSyncMessage) {
	logger.WithField("moonID", moonID).Debug("moon sync")

	// 如果数据包内一个zone没有，则返回该moon下所有zone的列表
	if len(msg.Message) == 0 {
		logger.Debug("empty sync list, send all list back")
		dao := moon.New()
		dao.ID = moonID
		list, err := dao.ListTargetZone()
		if err != nil {
			logger.WithError(err).Error()
		} else {
			err = sendZoneSync(list, moonID)
			if err != nil {
				logger.WithError(err).Error()
			}
		}
		return
	}

	outDatedList := make([]*model.Zone, 0)
	for _, msg := range msg.Message {
		v, ok := zoner.zoneVersion.Load(msg.ZoneID)
		if !ok {
			logger.WithField("id", msg.ZoneID).WithField("moonID", moonID).Warning("unknown zone")
			continue
		}
		version := v.(int)
		if int32(version) != msg.CurrentVersion {
			logger.WithField("moonID", moonID).WithField("zoneID", msg.ZoneID).Debug("zone version outdated")

			outDatedList = append(outDatedList, &model.Zone{
				ID:      msg.ZoneID,
				Version: version,
			})
		}
	}
	if err := sendZoneSync(outDatedList, moonID); err != nil {
		logger.WithError(err).Error("failed to send sync message")
	}
}

func (zoner *Zoner) deploy(zoneID string) {
	dao := zone.New()
	// 获取moon节点
	dao.ID = zoneID
	z, err := dao.GetDetails()
	if err != nil {
		return
	}

	zoner.zoneVersion.Store(zoneID, z.Version)

	logger.WithField("zoneID", zoneID).WithField("version", z.Version).Debug("deploying")
	logger.Debugf("deploy to %d moons", len(z.Moons))
	syncMessage := pb.ZoneSyncMessage{}
	buf := pool.Fixed64KPool.Get().([]byte)

	syncMessage.Message = append(syncMessage.Message, &pb.ZoneSyncSingle{
		ZoneID:         zoneID,
		CurrentVersion: int32(z.Version),
	})
	n, err := syncMessage.MarshalTo(buf)
	defer pool.Fixed64KPool.Put(buf)
	if err != nil {
		return
	}
	msg := model2.NewMessage(ModuleName, "zone")
	msg.Content = buf[:n]
	msg.Operation = c.OpSync
	planetMsg := &model2.PlanetMessage{
		Message: &msg,
	}
	for _, m := range z.Moons {
		logger.WithField("moonID", m.ID).Debug("send to moon")
		planetMsg.MoonID = m.ID
		_ = context.SendMessage(planetMsg)
	}
}

func sendZoneSync(zoneList []*model.Zone, moonID string) error {
	syncMessage := pb.ZoneSyncMessage{}

	for _, val := range zoneList {
		syncMessage.Message = append(syncMessage.Message, &pb.ZoneSyncSingle{
			ZoneID:         val.ID,
			CurrentVersion: int32(val.Version),
			State:          int32(val.State),
		})
	}
	buffer, _ := syncMessage.Marshal()

	msg := model2.NewMessage(ModuleName, "zone")
	msg.Content = buffer
	msg.Operation = c.OpSync
	return context.SendMessage(&model2.PlanetMessage{
		Message: &msg,
		MoonID:  moonID,
	})
}

func sendLeave(zoneID, moonID string) error {
	deployMsg := pb.ZoneLeave{
		ZoneID: zoneID,
	}
	buffer, err := deployMsg.Marshal()
	if err != nil {
		return err
	}
	msg := model2.NewMessage(ModuleName, "zone")
	msg.Operation = c.OpLeave
	msg.Content = buffer
	return context.SendMessage(&model2.PlanetMessage{
		Message: &msg,
		MoonID:  moonID,
	})
}

func GetContainers(moonID string) (pb.MoonContainerList, error) {
	msg := model2.NewMessage(ModuleName, "zone")
	msg.Operation = c.OpGetContainers

	ret, err := context.SyncSendMessage(&model2.PlanetMessage{
		Message: &msg,
		MoonID:  moonID,
	}, time.Second*10)

	if err != nil {
		return pb.MoonContainerList{}, err
	}
	containerMsg := pb.MoonContainerList{}
	err = containerMsg.Unmarshal(ret.Content.([]byte))
	if err != nil {
		logger.WithError(err).Error()
		return pb.MoonContainerList{}, err
	}
	return containerMsg, nil
}

func Deploy(zoneID string) error {
	go localZoner.deploy(zoneID)
	return nil
}

func Leave(zoneID, moonID string) error {
	go sendLeave(zoneID, moonID)
	return nil
}
